#include <phpcpp.h>
#include <armadillo>
#include <stdio.h>

using namespace std;
using namespace arma;

/**
 * compute: alpha * A * B + beta * C
 */
Php::Value mulplus(Php::Parameters &params)
{    
    int ps = (int) params.size();
    
    // init weight
    double alpha = (double) params[0];
    Php::Array a = params[1];
    int m = Php::count(a);
    int n = Php::count(a[0]);
    int k = n;
    double beta = 0;
    bool debug = false;

    // init matrix
    Mat<double> A = mat();
    Mat<double> B = mat(n, n);
    B.eye();
    Mat<double> C = mat(m, n);
    C.zeros();

    for (int i = 0; i < m; i++) {
	vector<double> v = Php::array_values(a[i]);
	Row<double> r = rowvec(v);
	A.insert_rows(i, r);
    }

    // parse params
    if (ps > 2) {
	Php::Array b = params[2];
	int m1 = Php::count(b);
	k = Php::count(b[0]);
	C.set_size(m, k);
	C.zeros();
	B = mat();
	for (int i = 0; i < m1; i++) {
	    vector<double> v = Php::array_values(b[i]);
	    Row<double> r = rowvec(v);
	    B.insert_rows(i, r);
	}
	if (ps > 3) {
	    beta = (double) params[3];
	    C.ones();
	    if (ps > 4) {
		Php::Array c = params[4];
		int m2 = Php::count(c);
		C = mat();
		for (int i = 0; i < m2; i++) {
		    vector<double> v = Php::array_values(c[i]);
		    Row<double> r = rowvec(v);
		    C.insert_rows(i, r);
		}
		if (ps > 5) {
		    debug = (bool) params[5];
		}
	    }
	}
    }

    // compute
    Mat<double> R = alpha * A * B + beta * C;
    
    // show debug info
    if (debug) {
	cout << "alpha: " << alpha << ", beta: " << beta << endl
	     << "A" << endl << A << endl << "B"<< endl << B << endl
	     << "C" << endl << C << endl << "R" << R << endl;
    }

    // result for php
    Php::Array result;
    for (unsigned int i = 0; i < R.n_rows; i++) {
	vector<double> r = conv_to<vector<double>>::from(R.row(i));
	result[i] = r;
    }
    return result;
 }

/**
 *  tell the compiler that the get_module is a pure C function
 */
extern "C" {
    
    /**
     *  Function that is called by PHP right after the PHP process
     *  has started, and that returns an address of an internal PHP
     *  strucure with all the details and features of your extension
     *
     *  @return void*   a pointer to an address that is understood by PHP
     */
    PHPCPP_EXPORT void *get_module() 
    {
        // static(!) Php::Extension object that should stay in memory
        // for the entire duration of the process (that's why it's static)
        static Php::Extension extension("linalg", "1.0");
        
        // @todo    add your own functions, classes, namespaces to the extension
	extension.add<mulplus>("mulplus", {
		Php::ByVal("alpha", Php::Type::Float),
		Php::ByVal("a", Php::Type::Array),
		Php::ByVal("b", Php::Type::Array, false),
		Php::ByVal("beta", Php::Type::Float, false),
		Php::ByVal("c", Php::Type::Array, false),
		Php::ByVal("debug", Php::Type::Bool, false),
	    });
        // return the extension
        return extension;
    }
}
